package de.fuberlin.inf.alp3.ue06;

import java.util.EmptyStackException;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
import de.fuberlin.inf.alp3.utils.ConsoleInput;

public class TestArithmeticExpression {
    // boolean variables
    private static Boolean[] booleans = new Boolean[3];
    // integer variables
    private static Integer[] integer = new Integer[9];
    // supported operations
    private static Map<Character, java.lang.Integer> operations;
    
    /**
     * This method generates postfix-listed characters depending to the passed
     * infix string.
     * @param the string in infix notation
     * @return postfix-listed characters
     */
    private static java.util.LinkedList<Character> infixToPostfix(String str) {
        Stack<Character> stack = new Stack<Character>();
        java.util.LinkedList<Character> postfix = 
            new java.util.LinkedList<Character>();
        int i = 0;
        char infix = str.charAt(i);
        try {
            while (str.length() > i) {
                if ((infix >= 'a' && infix <= 'c') ||
                        (infix >= 'r' && infix <= 'z')) {
                    postfix.add(infix);
                    infix = str.charAt(++i);
                }
                else if (infix == '(') {
                    stack.push(infix);
                    infix = str.charAt(++i);
                }
                else if (infix == ')') {
                    char nextOp;
                    while ('(' != (nextOp = stack.pop()))
                        postfix.add(nextOp);
                    infix = str.charAt(++i);
                }
                else if (operations.containsKey(infix)) {
                    if (stack.empty())
                        stack.push(infix);
                    else {
                        while ((!stack.empty()) && (stack.peek() != '(') && 
                        (operations.get(stack.peek()) > operations.get(infix)))
                            postfix.add(stack.pop());
                        stack.push(infix);
                    }
                    infix = str.charAt(++i);
                }
                else if (infix == ':') {
                    while ('?' != stack.peek())
                        postfix.add(stack.pop());
                    infix = str.charAt(++i);
                }
                else {
                    throw new RuntimeException("Invalid syntax (char " + infix +
                            " not valid).");
                }
            }
        } catch (StringIndexOutOfBoundsException e) { /* end reached */ }
        while (!stack.empty())
            postfix.add(stack.pop());
        return postfix;
    }
    
    /**
     * This method delivers an expression tree for postfix-listed characters 
     * @param the postfix-listed characters
     * @return the expression tree
     */
    private static Expression postfixToExpressionTree(
            java.util.LinkedList<Character> postfix) {
        Stack<Expression> expStack = new Stack<Expression>();
        Expression first, second, third;
        
        for (int i = 0; i < postfix.size(); i++) {
            try {
                switch (postfix.get(i)) {
                case '*':
                    second = expStack.pop();
                    first  = expStack.pop();
                    expStack.push(new Multiplication(first, second));
                    break;
                case '/':
                    second = expStack.pop();
                    first  = expStack.pop();
                    expStack.push(new Division(first, second));
                    break;
                case '+':
                    second = expStack.pop();
                    first  = expStack.pop();
                    expStack.push(new Addition(first, second));
                    break;
                case '-':
                    second = expStack.pop();
                    first  = expStack.pop();
                    expStack.push(new Subtraction(first, second));
                    break;
                case '=':
                    second = expStack.pop();
                    first  = expStack.pop();
                    expStack.push(new Equal(first, second));
                    break;
                case '<':
                    second = expStack.pop();
                    first  = expStack.pop();
                    expStack.push(new Less(first, second));
                    break;
                case '>':
                    second = expStack.pop();
                    first  = expStack.pop();
                    expStack.push(new Greater(first, second));
                    break;
                case '?':
                    third  = expStack.pop();
                    second = expStack.pop();
                    first  = expStack.pop();
                    expStack.push(new Triadic(first, second, third));
                    break;
                default:
                    expStack.push(variable(postfix.get(i)));
                    break;
                }
            } catch (EmptyStackException e) {
                throw new RuntimeException("Invalid syntax (wrong number of " +
                		"operands)");
            }
        }
        return expStack.pop();
    }
    
    /**
     * This method delivers an expression object representing a variable
     * specified by the passed character.
     * @param the character (a-c for boolean, r-z for integer)
     * @return the expression object
     */
    private static Expression variable(char c) {
        if (c >= 'a' && c <= 'c') {
            if (null == booleans[c - 'a'])
                booleans[c - 'a'] = new Boolean(
                        ConsoleInput.readBoolean("Value of variable " + c + "?",
                                "Try again!"));
            return booleans[c - 'a'];
        }
        else if (c >= 'r' && c <= 'z') {
            if (null == integer[c - 'r'])
                integer[c - 'r'] = new Integer(
                        ConsoleInput.readInt("Value of variable " + c + "?",
                                "Try again!"));
            return integer[c - 'r'];
        }
        else
            throw new RuntimeException();
    }
    
    /**
     * This method generates an expression tree representing the infix noted
     * term in the passed string parameter.
     * @param the infix noted string
     * @return the expression tree representation
     */
    private static Expression stringToExpression(String str) {
        operations = new HashMap<Character, java.lang.Integer>();
        operations.put('*', 3);
        operations.put('/', 3);
        operations.put('+', 2);
        operations.put('-', 2);
        operations.put('=', 1);
        operations.put('<', 1);
        operations.put('>', 1);
        operations.put('?', 0);
        java.util.LinkedList<Character> postfix = infixToPostfix(str);
        return postfixToExpressionTree(postfix);
    }
    
    public static void main(String[] args) {
        String input;
        input = ConsoleInput.readString("Type in the expression!");
        try {
            Expression tree = stringToExpression(input);
            System.out.println(tree.eval());
        } catch (RuntimeException e) {
            System.out.println("ERROR:");
            System.out.println(e.getMessage());
        }
    }
}